package list

import (
	"gitee.com/Billcoding/gotypes"
	"sync"
)

//Define LinkedElement struct
type LinkedElement struct {
	Prev  *gotypes.Element
	Value *gotypes.Element
	Next  *gotypes.Element
}

//Define LinkedList struct
type LinkedList struct {
	//A sync locker
	mutex sync.Mutex
	//LinkedElements
	elements []*LinkedElement
}

//create new empty List
func NewLinkedList() *LinkedList {
	elements := make([]*LinkedElement, 0)
	return &LinkedList{
		elements: elements,
		mutex:    sync.Mutex{},
	}
}

//clear List
func (linkedList *LinkedList) Clear() {
	linkedList.elements = make([]*LinkedElement, 0)
}

//return List size
func (linkedList *LinkedList) Size() int {
	return len(linkedList.elements)
}

//add element into tail
func (linkedList *LinkedList) Add(e gotypes.Element) *LinkedList {
	linkedList.mutex.Lock()
	defer linkedList.mutex.Unlock()
	var prevLinkedElement *LinkedElement
	if linkedList.Size() > 0 {
		prevLinkedElement = linkedList.elements[linkedList.Size()-1]
	}

	var prev *gotypes.Element
	if prevLinkedElement != nil {
		prev = prevLinkedElement.Value
	}

	current := &LinkedElement{
		Prev:  prev,
		Value: &e,
		Next:  nil,
	}

	if prevLinkedElement != nil {
		prevLinkedElement.Next = current.Value
	}

	linkedList.elements = append(linkedList.elements, current)
	return linkedList
}

//return first element
func (linkedList *LinkedList) Head() *LinkedElement {
	var e *LinkedElement
	linkedList.mutex.Lock()
	defer linkedList.mutex.Unlock()
	if linkedList.Size() > 0 {
		e = linkedList.elements[0]
	}
	return e
}

//return last element
func (linkedList *LinkedList) Tail() *LinkedElement {
	var e *LinkedElement
	linkedList.mutex.Lock()
	defer linkedList.mutex.Unlock()
	size := linkedList.Size()
	if size > 0 {
		e = linkedList.elements[size-1]
	}
	return e
}

//return head and remove it
func (linkedList *LinkedList) Take() *LinkedElement {
	var e *LinkedElement
	linkedList.mutex.Lock()
	defer linkedList.mutex.Unlock()
	size := linkedList.Size()
	if size > 0 {
		e = linkedList.elements[0]
		es := make([]*LinkedElement, 0)
		es = append(es, linkedList.elements[1:]...)
		linkedList.elements = es
	}
	return e
}

//return tail and remove it
func (linkedList *LinkedList) Peek() *LinkedElement {
	var e *LinkedElement
	linkedList.mutex.Lock()
	defer linkedList.mutex.Unlock()
	size := linkedList.Size()
	if size > 0 {
		e = linkedList.elements[size-1]
		es := make([]*LinkedElement, 0)
		es = append(es, linkedList.elements[:size-1]...)
		linkedList.elements = es
	}
	return e
}

//foreach function
func (linkedList *LinkedList) ForEach(fn func(e *LinkedElement)) {
	for _, e := range linkedList.elements {
		fn(e)
	}
}

//map function
func (linkedList *LinkedList) Map(mapFn func(e *LinkedElement) gotypes.Element) *LinkedList {
	return linkedList.fn(func(e *LinkedElement) bool { return true }, mapFn)
}

//filter function
func (linkedList *LinkedList) Filter(filterFn func(e *LinkedElement) bool) *LinkedList {
	return linkedList.fn(filterFn, func(e *LinkedElement) gotypes.Element {
		return *e.Value
	})
}

//reduce function
func (linkedList *LinkedList) Reduce(val gotypes.Element, reduceFn func(val gotypes.Element, e *LinkedElement) gotypes.Element) gotypes.Element {
	firstPtr := &val
	linkedList.ForEach(func(e *LinkedElement) {
		*firstPtr = reduceFn(*firstPtr, e)
	})
	return *firstPtr
}

//matchAny function
func (linkedList *LinkedList) MatchAny(matchAnyFn func(e *LinkedElement) bool) bool {
	for _, e := range linkedList.elements {
		if matchAnyFn(e) {
			return true
		}
	}
	return false
}

//matchNone function
func (linkedList *LinkedList) MatchNone(matchNoneFn func(e *LinkedElement) bool) bool {
	for _, e := range linkedList.elements {
		if matchNoneFn(e) {
			continue
		}
		return true
	}
	return false
}

//matchAll function
func (linkedList *LinkedList) MatchAll(matchAllFn func(e *LinkedElement) bool) bool {
	count := 0
	for _, e := range linkedList.elements {
		if matchAllFn(e) {
			count++
		}
	}
	return count == linkedList.Size()
}

func (linkedList *LinkedList) fn(filterFn func(e *LinkedElement) bool, mapFn func(e *LinkedElement) gotypes.Element) *LinkedList {
	newLinkedList := NewLinkedList()
	for _, e := range linkedList.elements {
		if filterFn(e) {
			newLinkedList.Add(mapFn(e))
		}
	}
	return newLinkedList
}
